import java.util.ArrayList;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * Model store the sudoku game elements in to a 9x9 int array, and solve the sudoku game
 * @author Jiajie Wu
 *
 */
public class Model {
	private final int SIZE = 9;
	
	private int[][] sudoku_element;
	ArrayList<ChangeListener> listeners;
	
	private int[][] element_status;
	private int[][][] availableNum;
	/**
	 * Construct a Model
	 */
	public Model(int[][] init) {
		sudoku_element = init;
		listeners = new ArrayList<ChangeListener>();
		
		element_status = new int[SIZE][SIZE];
		availableNum = new int[SIZE][SIZE][SIZE];	
		
		for (int i = 0; i < SIZE; i++) {
			for (int j = 0; j < SIZE; j++) {
				if (sudoku_element[i][j] > 0) {
					element_status[i][j] = 1;
				} else {
					element_status[i][j] = 0;
				}
				
				for (int k = 0; k < SIZE; k++) {
					availableNum[i][j][k] = 0;
				}
			}
		}
	}
	
	/**
	 * To the the sudoku result
	 * @return sudoku result stored in int[][]
	 */
	public int[][] getSudoku() {
		return sudoku_element;
	}
	
	public void attach(ChangeListener l) {
		listeners.add(l);
	}
	
	/**
	 * Solve the Sudoku, sotre the result in sudoku_element
	 * @param arg1 nothing
	 * @param arg2 nothing
	 */
	public void solve(int arg1, int arg2) {
		this.solve();
		for (ChangeListener l : listeners) {
			l.stateChanged(new ChangeEvent(this));
		}
	}
	
	/**
	 * Solve sudoku
	 */
	public void solve() {
	    int m = 0, n = 0;
	    while(m < SIZE && n < SIZE) {
	        int i, j = 0;
	        for( i = 0; i < SIZE; i++) {
	            for( j = 0; j < SIZE; j++) {
	                if(sudoku_element[i][j] == 0) {
	                    break;
	                }
	            }
	            
	            //added specificly to java version to avoid ArrayOutOfBound Exception, not in javascript version
	            if (j >= SIZE - 1) j = SIZE - 1;
	            
	            if(j < SIZE - 1) {
	                break;
	            } else if(sudoku_element[i][j] == 0) {
	                break;
	            }
	        }
	        if(i == 8 && j == 8 && sudoku_element[i][j] != 0) {
	            return;
	        }
	        m = i;
	        n = j;
	        if(i < SIZE && j < SIZE) {
	            check(i, j);

	            int count;
	            for( count = 0; count < SIZE; count++) {
	                if(availableNum[i][j][count] == 0) {
	                    //sudoku_element[i][j] = count + 1;
	                    break;
	                }
	            }
	            if(count < SIZE) {
	                int[] random_choice = new int[SIZE];
	                for(int k = 0; k < SIZE; k++) {
	                    random_choice[k] = 0;
	                }
	                int pos = 0;
	                for(int k = 0; k < SIZE; k++) {
	                    if(availableNum[i][j][k] == 0) {
	                        random_choice[pos] = k + 1;
	                        pos++;
	                    }
	                }
	                int random_pos = (int) Math.floor(Math.random() * pos);
	                sudoku_element[i][j] = random_choice[random_pos];
	            } else if(count == SIZE) {
	                if(j == 0) {
	                    availableNum[i - 1][SIZE - 1][sudoku_element[i - 1][SIZE - 1] - 1] = -1;
	                    sudoku_element[i - 1][SIZE - 1] = 0;
	                    for(int k = 0; k < SIZE; k++) {
	                        availableNum[i][j][k] = 0;
	                    }
	                } else {
	                    int cMove = j - 1;
	                    int rMove = i;
	                    while(element_status[(rMove % SIZE)][(cMove % SIZE)] == 1){
	                        cMove--;
	                        if(cMove < 0){
	                            cMove = SIZE - 1;
	                            rMove = rMove - 1;
	                        }
	                        if(rMove < 0){
	                            return;
	                        }
	                    }
	                    availableNum[rMove][cMove][sudoku_element[rMove][cMove] - 1] = -1;
	                    sudoku_element[rMove][cMove] = 0;
	                    for(int k = 0; k < SIZE; k++) {
	                        availableNum[i][j][k] = 0;
	                    }

	                }
	            }
	        } else {
	            return;
	        }
	    }
	}
	
	/**
	 * Check the available number for each box
	 * @param row row number of specific box
	 * @param col column number of specific box
	 */
	public void check(int row, int col) {
	    if(sudoku_element[row][col] >= 0) {
	        //check for the number availability in the same row
	        for(int count = 0; count < SIZE; count++) {
	            if(count == col) {
	                continue;
	            }
	            if(sudoku_element[row][count] > 0 && availableNum[row][col][sudoku_element[row][count] - 1] == 0) {
	                availableNum[row][col][sudoku_element[row][count] - 1] = 1;
	            }
	        }

	        //check for the number availability in the same column
	        for(int count = 0; count < SIZE; count++) {
	            if(count == row) {
	                continue;
	            }
	            if(sudoku_element[count][col] > 0 && availableNum[row][col][sudoku_element[count][col] - 1] == 0) {
	                availableNum[row][col][sudoku_element[count][col] - 1] = 1;
	            }
	        }

	        //check for the number availability in the same sub board
	        int subboard_size = SIZE / 3;
	        int rStart = (int) Math.floor(row / subboard_size);
	        int cStart = (int) Math.floor(col / subboard_size);
	        int rTimes = 0;
	        int cTimes = 0;
	        for(int rCount = rStart * subboard_size; rTimes < subboard_size; rCount++, rTimes++) {
	            for(int cCount = cStart * subboard_size; cTimes < subboard_size; cCount++, cTimes++) {
	                if(rCount == row && cCount == col) {
	                    continue;
	                }
	                if(sudoku_element[rCount][cCount] > 0 && availableNum[row][col][sudoku_element[rCount][cCount] - 1] == 0) {
	                    availableNum[row][col][sudoku_element[rCount][cCount] - 1] = 1;
	                }
	            }
	            cTimes = 0;
	        }
	    }

	}
	
}
